// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/android/jni_array.h"

#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "base/logging.h"

namespace base {
namespace android {
    namespace {

        // As |GetArrayLength| makes no guarantees about the returned value (e.g., it
        // may be -1 if |array| is not a valid Java array), provide a safe wrapper
        // that always returns a valid, non-negative size.
        template <typename JavaArrayType>
        size_t SafeGetArrayLength(JNIEnv* env, JavaArrayType jarray)
        {
            DCHECK(jarray);
            jsize length = env->GetArrayLength(jarray);
            DCHECK_GE(length, 0) << "Invalid array length: " << length;
            return static_cast<size_t>(std::max(0, length));
        }

    } // namespace

    ScopedJavaLocalRef<jbyteArray> ToJavaByteArray(JNIEnv* env,
        const uint8_t* bytes,
        size_t len)
    {
        jbyteArray byte_array = env->NewByteArray(len);
        CheckException(env);
        DCHECK(byte_array);

        env->SetByteArrayRegion(
            byte_array, 0, len, reinterpret_cast<const jbyte*>(bytes));
        CheckException(env);

        return ScopedJavaLocalRef<jbyteArray>(env, byte_array);
    }

    ScopedJavaLocalRef<jbyteArray> ToJavaByteArray(
        JNIEnv* env,
        const std::vector<uint8_t>& bytes)
    {
        return ToJavaByteArray(env, bytes.data(), bytes.size());
    }

    ScopedJavaLocalRef<jintArray> ToJavaIntArray(
        JNIEnv* env, const int* ints, size_t len)
    {
        jintArray int_array = env->NewIntArray(len);
        CheckException(env);
        DCHECK(int_array);

        env->SetIntArrayRegion(
            int_array, 0, len, reinterpret_cast<const jint*>(ints));
        CheckException(env);

        return ScopedJavaLocalRef<jintArray>(env, int_array);
    }

    ScopedJavaLocalRef<jintArray> ToJavaIntArray(
        JNIEnv* env, const std::vector<int>& ints)
    {
        return ToJavaIntArray(env, ints.data(), ints.size());
    }

    ScopedJavaLocalRef<jlongArray> ToJavaLongArray(JNIEnv* env,
        const int64_t* longs,
        size_t len)
    {
        jlongArray long_array = env->NewLongArray(len);
        CheckException(env);
        DCHECK(long_array);

        env->SetLongArrayRegion(
            long_array, 0, len, reinterpret_cast<const jlong*>(longs));
        CheckException(env);

        return ScopedJavaLocalRef<jlongArray>(env, long_array);
    }

    // Returns a new Java long array converted from the given int64_t array.
    BASE_EXPORT ScopedJavaLocalRef<jlongArray> ToJavaLongArray(
        JNIEnv* env,
        const std::vector<int64_t>& longs)
    {
        return ToJavaLongArray(env, longs.data(), longs.size());
    }

    // Returns a new Java float array converted from the given C++ float array.
    BASE_EXPORT ScopedJavaLocalRef<jfloatArray> ToJavaFloatArray(
        JNIEnv* env, const float* floats, size_t len)
    {
        jfloatArray float_array = env->NewFloatArray(len);
        CheckException(env);
        DCHECK(float_array);

        env->SetFloatArrayRegion(
            float_array, 0, len, reinterpret_cast<const jfloat*>(floats));
        CheckException(env);

        return ScopedJavaLocalRef<jfloatArray>(env, float_array);
    }

    BASE_EXPORT ScopedJavaLocalRef<jfloatArray> ToJavaFloatArray(
        JNIEnv* env,
        const std::vector<float>& floats)
    {
        return ToJavaFloatArray(env, floats.data(), floats.size());
    }

    ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfByteArray(
        JNIEnv* env, const std::vector<std::string>& v)
    {
        ScopedJavaLocalRef<jclass> byte_array_clazz = GetClass(env, "[B");
        jobjectArray joa = env->NewObjectArray(v.size(),
            byte_array_clazz.obj(), NULL);
        CheckException(env);

        for (size_t i = 0; i < v.size(); ++i) {
            ScopedJavaLocalRef<jbyteArray> byte_array = ToJavaByteArray(
                env, reinterpret_cast<const uint8_t*>(v[i].data()), v[i].length());
            env->SetObjectArrayElement(joa, i, byte_array.obj());
        }
        return ScopedJavaLocalRef<jobjectArray>(env, joa);
    }

    ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfStrings(
        JNIEnv* env, const std::vector<std::string>& v)
    {
        ScopedJavaLocalRef<jclass> string_clazz = GetClass(env, "java/lang/String");
        jobjectArray joa = env->NewObjectArray(v.size(), string_clazz.obj(), NULL);
        CheckException(env);

        for (size_t i = 0; i < v.size(); ++i) {
            ScopedJavaLocalRef<jstring> item = ConvertUTF8ToJavaString(env, v[i]);
            env->SetObjectArrayElement(joa, i, item.obj());
        }
        return ScopedJavaLocalRef<jobjectArray>(env, joa);
    }

    ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfStrings(
        JNIEnv* env, const std::vector<string16>& v)
    {
        ScopedJavaLocalRef<jclass> string_clazz = GetClass(env, "java/lang/String");
        jobjectArray joa = env->NewObjectArray(v.size(), string_clazz.obj(), NULL);
        CheckException(env);

        for (size_t i = 0; i < v.size(); ++i) {
            ScopedJavaLocalRef<jstring> item = ConvertUTF16ToJavaString(env, v[i]);
            env->SetObjectArrayElement(joa, i, item.obj());
        }
        return ScopedJavaLocalRef<jobjectArray>(env, joa);
    }

    void AppendJavaStringArrayToStringVector(JNIEnv* env,
        jobjectArray array,
        std::vector<string16>* out)
    {
        DCHECK(out);
        if (!array)
            return;
        size_t len = SafeGetArrayLength(env, array);
        size_t back = out->size();
        out->resize(back + len);
        for (size_t i = 0; i < len; ++i) {
            ScopedJavaLocalRef<jstring> str(env,
                static_cast<jstring>(env->GetObjectArrayElement(array, i)));
            ConvertJavaStringToUTF16(env, str.obj(), &((*out)[back + i]));
        }
    }

    void AppendJavaStringArrayToStringVector(JNIEnv* env,
        jobjectArray array,
        std::vector<std::string>* out)
    {
        DCHECK(out);
        if (!array)
            return;
        size_t len = SafeGetArrayLength(env, array);
        size_t back = out->size();
        out->resize(back + len);
        for (size_t i = 0; i < len; ++i) {
            ScopedJavaLocalRef<jstring> str(env,
                static_cast<jstring>(env->GetObjectArrayElement(array, i)));
            ConvertJavaStringToUTF8(env, str.obj(), &((*out)[back + i]));
        }
    }

    void AppendJavaByteArrayToByteVector(JNIEnv* env,
        jbyteArray byte_array,
        std::vector<uint8_t>* out)
    {
        DCHECK(out);
        if (!byte_array)
            return;
        size_t len = SafeGetArrayLength(env, byte_array);
        if (!len)
            return;
        size_t back = out->size();
        out->resize(back + len);
        env->GetByteArrayRegion(byte_array, 0, len,
            reinterpret_cast<int8_t*>(&(*out)[back]));
    }

    void JavaByteArrayToByteVector(JNIEnv* env,
        jbyteArray byte_array,
        std::vector<uint8_t>* out)
    {
        DCHECK(out);
        DCHECK(byte_array);
        out->clear();
        AppendJavaByteArrayToByteVector(env, byte_array, out);
    }

    void JavaIntArrayToIntVector(JNIEnv* env,
        jintArray int_array,
        std::vector<int>* out)
    {
        DCHECK(out);
        size_t len = SafeGetArrayLength(env, int_array);
        out->resize(len);
        if (!len)
            return;
        // TODO(jdduke): Use |out->data()| for pointer access after switch to libc++,
        // both here and in the other conversion routines. See crbug.com/427718.
        env->GetIntArrayRegion(int_array, 0, len, &(*out)[0]);
    }

    void JavaLongArrayToInt64Vector(JNIEnv* env,
        jlongArray long_array,
        std::vector<int64_t>* out)
    {
        DCHECK(out);
        std::vector<jlong> temp;
        JavaLongArrayToLongVector(env, long_array, &temp);
        out->resize(0);
        out->insert(out->begin(), temp.begin(), temp.end());
    }

    void JavaLongArrayToLongVector(JNIEnv* env,
        jlongArray long_array,
        std::vector<jlong>* out)
    {
        DCHECK(out);
        size_t len = SafeGetArrayLength(env, long_array);
        out->resize(len);
        if (!len)
            return;
        env->GetLongArrayRegion(long_array, 0, len, &(*out)[0]);
    }

    void JavaFloatArrayToFloatVector(JNIEnv* env,
        jfloatArray float_array,
        std::vector<float>* out)
    {
        DCHECK(out);
        size_t len = SafeGetArrayLength(env, float_array);
        out->resize(len);
        if (!len)
            return;
        env->GetFloatArrayRegion(float_array, 0, len, &(*out)[0]);
    }

    void JavaArrayOfByteArrayToStringVector(
        JNIEnv* env,
        jobjectArray array,
        std::vector<std::string>* out)
    {
        DCHECK(out);
        size_t len = SafeGetArrayLength(env, array);
        out->resize(len);
        for (size_t i = 0; i < len; ++i) {
            ScopedJavaLocalRef<jbyteArray> bytes_array(
                env, static_cast<jbyteArray>(env->GetObjectArrayElement(array, i)));
            jsize bytes_len = env->GetArrayLength(bytes_array.obj());
            jbyte* bytes = env->GetByteArrayElements(bytes_array.obj(), nullptr);
            (*out)[i].assign(reinterpret_cast<const char*>(bytes), bytes_len);
            env->ReleaseByteArrayElements(bytes_array.obj(), bytes, JNI_ABORT);
        }
    }

    void JavaArrayOfIntArrayToIntVector(
        JNIEnv* env,
        jobjectArray array,
        std::vector<std::vector<int>>* out)
    {
        DCHECK(out);
        size_t len = SafeGetArrayLength(env, array);
        out->resize(len);
        for (size_t i = 0; i < len; ++i) {
            ScopedJavaLocalRef<jintArray> int_array(
                env, static_cast<jintArray>(env->GetObjectArrayElement(array, i)));
            JavaIntArrayToIntVector(env, int_array.obj(), &((*out)[i]));
        }
    }

} // namespace android
} // namespace base
